import os
import shutil
import errno
import string
from typing import Optional
from contextlib import contextmanager
from pathvalidate import is_valid_filename, sanitize_filename
from common.io_utils.dirs import mkdir_p
from common.logger import logger


@contextmanager
def temp_file(
    file_name: str,
    base_dir: Optional[str] = None,
    remove: bool = True,
):
    """Yield a path of the temporary file, based on user specification.
    The temporary file will be removed by default.
    This function only returns the path; user will be in charge of creating the file;
    and by default this function will delete the temporary file if it is found in the previously returned path.

    Parameters
    ----------
    file_name : str
        Name and extension of the temporary file.
    base_dir : str or None, optional
        Path of directory where temporary file will be created by user.
        Default is None to use the current working directory.
    remove : bool, optional
        Whether to remove the temporary file, by default True to remove the temporary file.

    Yields
    -------
    temp_file_path : str
        Path of the temporary file, which user can use to create the file.

    Raises
    ------
    ValueError
        Raised when the given path already exist.
    Warnings
        Raised when temporary file removal failed.

    Reference
    --------
    Method on check file name could be found here: https://gist.github.com/seanh/93666

    Examples
    --------
    >>> # Do something in original directory
    >>> with temp_file(file_name,base_dir) as TempFilePath:
    >>>     # Create the actual temporary file. Work with it.
    >>> # temp file will be deleted by default. Work on something else.
    """
    # Check if file_name is indeed simply a file name
    valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
    suggested_file_name = "".join(c for c in file_name if c in valid_chars)
    suggested_file_name = suggested_file_name.replace(" ", "_")
    file_name
    if not is_valid_filename(file_name):
        raise ValueError(
            f"{file_name} is not a valid name. Consider using {sanitize_filename(file_name)}."
        )
    if base_dir is None:
        base_dir = os.getcwd()
    temp_file_path = os.path.join(base_dir, file_name)
    # if file already exist, raise error message
    if os.path.isfile(temp_file_path):
        raise ValueError(f"{temp_file_path} already exist")
    try:
        yield temp_file_path
    finally:
        if remove:
            try:
                os.remove(temp_file_path)
            except OSError as e:
                if e.errno == errno.ENOENT:
                    logger.warning(
                        f"Temporary file ({temp_file_path}) can't be found. No file were removed."
                    )
                elif e.errno == errno.EACCES:
                    logger.warning(
                        f"Temporary file ({temp_file_path}) can't be deleted. Permission denied."
                    )
                else:
                    logger.warning(
                        f"Temporary file ({temp_file_path}) can't be deleted. Unknown error: {str(e)}"
                    )


@contextmanager
def temp_dir(
    dir_name: str,
    base_dir: Optional[str] = None,
    remove: bool = True,
    remove_exist: bool = False,
):
    """Create a temporary folder and yield the path, based on user specification.
    The temporary folder will be removed by default.

    Parameters
    ----------
    dir_name : str
        Name of the folder.
    base_dir : str or None, optional
        Path of directory where temporary folder will be created.
        Default is None to use the current working directory.
    remove : bool, optional
        Whether to remove the temporary file, by default True to remove the temporary file.
    remove_exist : bool, optional
        Remove the temporary folder if it exists before folder creation, by default False.

    Yields
    -------
    temp_folder_path : str
        Path of the temporary folder.

    Raises
    ------
    ValueError
        Raised when the given path already exist.
    Warnings
        Raised when temporary folder removal failed.

    Examples
    --------
    >>> # Do something in original directory
    >>> with temp_dir(dir_name, base_dir) as TempFolderPath:
    >>>     # Do something with the folder. Work with it.
    >>> # temp folder and its content will be deleted by default. Work on something else.
    """
    if base_dir is None:
        base_dir = os.getcwd()
    temp_folder_path = os.path.join(base_dir, dir_name)

    # if file already exist, raise error message
    if os.path.isdir(temp_folder_path):
        if remove_exist:
            shutil.rmtree(temp_folder_path)
        else:
            raise ValueError(f"{temp_folder_path} already exist")

    mkdir_p(temp_folder_path)
    try:
        yield temp_folder_path
    finally:
        if remove:
            try:
                shutil.rmtree(temp_folder_path)
            # this would be "except OSError, e:" before Python 2.6
            except OSError as e:
                # errno.ENOENT = no such file or directory
                if e.errno == errno.ENOENT:
                    logger.warning(
                        f"Temporary folder ({temp_folder_path}) can't be found. No folder was removed."
                    )
                elif e.errno == errno.EACCES:
                    logger.warning(
                        f"Temporary folder ({temp_folder_path}) can't be deleted. Permission denied."
                    )
                else:
                    logger.warning(
                        f"Temporary folder ({temp_folder_path}) can't be deleted. Unknown error: {str(e)}"
                    )
